home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CD ROM Paradise Collection 4
/
CD ROM Paradise Collection 4 1995 Nov.iso
/
program
/
tjgold.zip
/
INSTALL.001
/
DFBTREE.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1995-07-19
|
61KB
|
1,355 lines
{--------------------------------------------------------------------------}
{ Product: TechnoJock's Turbo Toolkit }
{ Version: GOLD }
{ Build: 1.01 }
{ }
{ The index routines used in TTT Gold were developed by Dean Farwell II }
{ and are an adaptation of his excellent TBTREE database tools. }
{ }
{ Copyright 1988-1994 Dean Farwell II }
{ Portions Copyright 1986-1995 TechnoJock Software, Inc. }
{ All Rights Reserved }
{ Restricted by License }
{--------------------------------------------------------------------------}
{********************************}
{ Unit: DFBTREE }
{********************************}
unit DFBtree;
(*****************************************************************************)
(* *)
(* B T R E E R O U T I N E S *)
(* *)
(*****************************************************************************)
(*////////////////////////// I N T E R F A C E //////////////////////////////*)
interface
uses
DFBTreUt;
const
MAXVALSIZE = 245; (* max value size in index *)
type
VSizeType = 1 .. MAXVALSIZE; (* size range for index entries *)
ValidationError = (NOERROR,PRECERROR,IFILEERROR);
ValueArray = Array [VSizeType] of Byte;
TreeCursor = record
prNum : PrNumber;
entryNum : Byte;
valid : Boolean;
indexField : Integer;
end;
(* The following files are located in BTREE3.INC *)
(* This routine will set the tree cursor to the front of the index. In
other words, it will point to the first entry in the index. Remember, the
index is ordered by the value of each entry. It will also return the
logical record associated with the first entry in the index. It will
return 0 only if there is no first entry (the index is empty). This
routine should be called if you want to start at the beginning of an index
and want to retrieve logical record numbers in order of entry. *)
function UsingCursorGetFirstLr(iFName : FnString;
var fId : File (* var for speed only *)
) : LrNumber;
(* This routine will set the tree cursor to the end of the index. In
other words, it will point to the first entry in the index. Remember, the
index is ordered by the value of each entry. It will also return the
logical record associated with the last entry in the index. It will
return 0 only if there is no last entry (the index is empty). This
routine should be called if you want to start at the end of an index
and want to retrieve logical record numbers in reverse order of entry. *)
function UsingCursorGetLastLr(iFName : FnString;
var fId : File (* var for speed only *)
) : LrNumber;
(* This routine will set the tree cursor to the end of the index. In other
words, it will point to the last entry in the index. Remember, the index
is ordered by the value of each entry. It will also return the logical
record associated with the last entry in the index. It will return 0 only
if there is no first entry (the index is empty). This routine should be
called if you want to start at the end of an index and want to retrieve
logical record numbers in order of entry. *)
(*\*)
(* This routine is the same as UsingCursorAndValueGetLr except that this
routine will set the tree cursor to the location of the first value in the
index which is greater than or equal to paramValue. It will also return
the logical record associated with this entry. It will return 0 if there
is no entry which is greater than or equal to this value. *)
function UsingCursorAndGEValueGetLr(iFName : FnString;
var fId : File; (* var for speed only *)
var paramValue;
partial : Boolean) : LrNumber;
(* This routine will move the cursor to the right one entry and return the
value associated with this entry. It will return 0 if the cursor was not
valid (not pointing to an entry) or if there is no next entry (you are at
end of index). This routine should be called if you want to move the
cursor to the next larger entry from the present cursor position and
retrieve the associated logical record number. This routine should not
normally be used until the cursor has been positioned using one of the
three previous positioning routines. *)
function UsingCursorGetNextLr(iFName : FnString;
var fId : File (* var for speed only *)
) : LrNumber;
(* This routine will move the cursor to the left one entry and return the
value associated with this entry. It will return 0 if the cursor was not
valid (not pointing to an entry) or if there is no next entry (you are at
end of index). This routine should be called if you want to move the
cursor to the next larger entry from the present cursor position and
retrieve the associated logical record number. This routine should not
normally be used until the cursor has been positioned using one of the
previous positioning routines. *)
function UsingCursorGetPrevLr(iFName : FnString;
var fId : File (* var for speed only *)
) : LrNumber;
(* This routine will not move the cursor. It will return the logical record
number associated with the current cursor position. It will return 0 only
if the current cursor position is not valid. *)
function UsingCursorGetCurrLr(iFName : FnString;
var fId : File (* var for speed only *)
) : LrNumber;
(* This routine will not move the cursor. It will return the index entry
(data value) associated with the current cursor position. If the current
cursor position is not valid, paramValue will be returned unchanged. You
can use UsingCursorGetCurrLr to check the cursor before calling this
routine, if desired. *)
procedure UsingCursorGetCurrValue(iFName : FnString;
var fId : File; (* var for speed only *)
var paramValue);
(* This routine will allow you to save a cursor in memory. The current state
of the cursor will be passed back to you in the parameter cursor. It is
handy if you want to keep track of where you are in a list or check values
associated with a cursor. *)
(* The following files are located in BTREE4.INC *)
(* This routine will create an index file with the file name as specified
by iFName. The valSize parameter specifies the size of the index
entries. The easiest way to determine this is to use the SizeOf
function. The valType parameter specifies the type for the index
entries. The types supported are those enumerated by the ValueType
enumerated type.
note - Extremely important - WARNING - for STRINGVALUE indexes only - the
valSize must be 1 greater than the number of characters of the longest
string. This will allow 1 byte for the string length to be stored.
for example - if 'abc' is the longest string then valSize = 4. *)
procedure CreateIndexFile(iFName : FnString;
var fId : File;
valSize : VSizeType;
valType : ValueType;
indexedField : Integer;
upperCase : Boolean);
(*\*)
(* This routine will insert a value and its associated logical record number
into the given index file. This routine will guard against duplicate
entries. An index should have no more than one occurence of any
lrNum,paramValue pair (no two entries match on paramValue and lrNum). This
routine assures this by calling DeleteValueFromBTree prior to performing
the insert. This will get rid of a previous occurence if it exists. *)
procedure InsertValueInBTree(iFName : FnString;
var fId : File; (* var for speed only *)
lrNum : LRNumber;
var paramValue);
procedure DeleteValueFromBTree(iFName : FnString;
var fId : File; (* var for speed only *)
lrNum : LrNumber;
var paramValue);
(* This routine will start at the root node and return the number of levels
that exist in a BTree. The index file name is the only required input. *)
function NumberOfBTreeLevels(iFName : FnString;
var fId : File (* var for speed only *)
) : Byte;
(* This routine will search an index and determine whether the given logical
record number is in the index. If it is, TRUE is returned in found and the
value associated with the logical record number is returned in paramValue.
If it is not found, found will be returned as FALSE and paramValue will
remain unchanged. This is primarily used for debugging or determining if
an index has somehow been damaged. *)
procedure FindLrNumInBTree(iFName : FnString;
var fId : File; (* var for speed only *)
lrNum : LrNumber;
var paramValue;
var found : Boolean);
(* This routine will return a count of the number of entries in the index. *)
function IndexEntryCount(iFName : FnString;
var fId : File (* var for speed only *)
) : LrNumber;
(* This routine will print out information regarding the index file. It is
designed to aid in my debugging, but is available for your use as well.
The nodeInfo paramter is used to specify whether you want the information
for each node in the index to be printed. *)
procedure PrintBTreeInfo(iFName : FnString;
var fId : File; (* var for speed only *)
nodeInfo : Boolean;
var lst : PrintTextDevice);
(* This routine returns the field number of the indexed field in support of
GoldDB *)
function GetIndexedField(iFName : FnString;
var fId : File) : Integer; (* var for speed only *)
(* This function returns the record number corresponding to the given entry
number. An entry number is the relative number from the beginning of the
index. In other words, entry number one is the first entry in the index.
It will return NULL if there is no corresponding record number. This can
only happen if entryNum > number of entries in the index. *)
function GetBTreeEntryLR(iFName : FnString;
var fId : File; (* var for speed only *)
entryNum : LrNumber) : LrNumber;
(* This routine returns TRUE if the index is all upper case *)
function GetUpperCaseFlag(iFName : FnString;
var fId : File) : Boolean; (* var for speed only *)
(* This routine will perform a partial or a full validation of an index file.
(depending on the value of the variable Partial). A partial check will
validate that the pRec record (record 0) is intact and that the file
structure is valid. A full validation will perform an additional check
to ensure that the data file and the index file are synchronized. The
routine will return one of the following values:
NOERROR
PRECERROR
IFILEERROR *)
function ValidateBTree(iFName : FnString;
var fId : File (* var for speed only *)
): ValidationError;
(*\*)
(*///////////////////// I M P L E M E N T A T I O N /////////////////////////*)
implementation
uses
DFPage;
(*****************************************************************************)
(* *)
(* I N S E R T / D E L E T E / M I S C B T R E E R O U T I N E S *)
(* *)
(*****************************************************************************)
(* These definitions and routines are the 'guts' of the BTREE unit. This
contain all routines which manipulate the index nodes (physical records)
and bitmaps, put values and logical record numbers in and out of the
indexes and perform other important functions. Most of the routines in
this file are internal to the BTREE unit, although several are not. *)
const
NULL : RecordNumber = 0; (* used to dilineate null
record number *)
VERSIONINFO = 'GOLDDB INDEX V1.0';
type
NodeType = (INVALIDNODETYPE,INDEXNODE,SEQUENCENODE);
(* INVALIDNODETYPE is not used for anything.
It serves one purpose. It gives positive
values to the two remaining legal values
for this enumerated type. I didn't want
to have zero be valid. This helped in
debugging and there is no reason to
change it. *)
(* These parameters are contained in the first record (0) in the index file
variable parameter type range
-------- --------- ---- -----
version version information String[20] N/A
nextAvail next available node NodePtrType 0 - MAXLONGINT
firstBMRec first bitmap record PrNumber 0 - MAXLONGINT
lastBMRec last bitmap record PrNumber 0 - MAXLONGINT
vSize value size Byte 1 - 245
rNode root node Longint 1 - MAXLONGINT
fSNode first sequence node Longint 1 - MAXLONGINT
lSNode last sequence node LongInt 1 - MAXLONGINT
vType value type ValueType 0 = INVALIDVALUE
1 = BYTEVALUE
2 = SHORTINTVALUE
3 = INTEGERVALUE
4 = LONGINTVALUE
5 = WORDVALUE
6 = STRINGVALUE
7 = REALVALUE
8 = SINGLEVALUE
9 = DOUBLEVALUE
10 = EXTENDEDVALUE
11 = COMPVALUE
12 = BYTEARRAYVALUE
cursor tree cursor info TreeCursor N/A
iField indexed field - Gold DB only
upperCaseFlag Gold DB only *)
(*\*)
type
NodePtrType = PrNumber; (* pointer to index records *)
ParameterRecord = record
version : String[20]; (* version info *)
nextAvail : NodePtrType; (* next index node available *)
firstBMRec : PrNumber; (* first record used for bitmap *)
lastBMRec : PrNumber; (* last record used for bitmap *)
vSize : VSizeType;
rNode : NodePtrType;
fSNode : NodePtrType;
lSNode : NodePtrType;
vType : ValueType;
cursor : TreeCursor;
iField : Integer; (* Indexed Field - Gold DB only *)
upperCaseFlag : Boolean; (* Gold DB only *)
end;
(* These parameters is found in every index and sequence node in the index
file.
variable parameter location size type range
-------- --------- -------- ---- ---- -----
prev prev sequence 503 4 int 0 - MAXINT
node
next next sequence 507 4 int 0 - MAXINT
node
nType node type 511 1 Byte 0 = INVALIDNODETYPE
1 = INDEXNODE
2 = SEQUENCENODE
vCnt value count 512 1 Byte 1 - MAXBYTE *)
const
PREVLOC = 503;
NEXTLOC = 507;
NTYPELOC = 511;
VCNTLOC = 512;
MAXUSABLE = 502; (* how much can be used for entries and record numbers *)
var
mustMoveCursor : Boolean;
(*\*)
(* This routine will return the record number for the first unused index
record (node). If the first unused node is the first used bitmap record
then the bitmap records will be moved down to free up disk space. The
number of physical pages freed up depends on the size of the index file *)
function FirstUnusedIndexRecord(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
var pRec : ParameterRecord) : NodePtrtype;
var
newRecord : NodePtrType; (* record number to be returned *)
recsToMove : PrNumber;
begin
newRecord := pRec.nextAvail; (* record number to return *)
pRec.nextAvail := FindNextAvailInBitmap(iFName,fId,pRec.firstBMRec,
pRec.lastBMRec,newRecord);
if BTreeErrorOccurred then Exit;
if newRecord = pRec.firstBMRec then
begin (* need to move bitmap records *)
if newRecord <= 4 then
begin
recsToMove := 1;
end
else
begin
if newRecord <= 10 then
begin
recsToMove := 3;
end
else
begin
recsToMove := 5;
end;
end;
MoveRecords(iFName,fId,pRec.firstBMRec,pRec.lastBMRec,recsToMove);
if BTreeErrorOccurred then Exit;
end;
FirstUnUsedIndexRecord := newRecord; (* record number to return *)
end; (* end of FirstUnusedIndexRecord routine *)
(* This routine will delete a node from the index file by setting the
appropriate bitmap bit to zero *)
procedure DeleteIndexRecord(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
thisNode : NodePtrType;
var pRec : ParameterRecord);
begin
SetBitInBitmap(iFName,fId,pRec.firstBMRec,thisNode,0); (* mark as unused *)
if BTreeErrorOccurred then Exit;
ReleasePage(iFName,thisNode); (* more for efficiency .. not required *)
if thisNode < pRec.nextAvail then
begin
pRec.nextAvail := thisNode;
end;
end; (* end of DeleteIndexRecord routine *)
(*\*)
(* This routine will insert a node between prevNode and nextNode in a node list.
It will accomplish this by setting the prev and next ptrs as necessary
for a node and its prev and next nodes. Obviously, the node ptr and the
next and prev node pointers must be known. If the node type is
SEQUENCENODE and this node is the first node in the sequential list, the
parameter record will be updated to reflect this change (the sNode parameter
will be set to this node ). *)
procedure InsertNodeInList(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
thisNode : NodePtrType;
var prevNode;
var nextNode;
var pRec : ParameterRecord);
var
pg : SinglePage;
tempPrevNode,
tempNextNode : NodePtrType;
begin
Move(prevNode,tempPrevNode,SizeOf(NodePtrType));
Move(nextNode,tempNextNode,SizeOf(NodePtrType));
FetchPage(iFName,fId,thisNode,pg);
if BTreeErrorOccurred then Exit;
Move(prevNode,pg[PREVLOC],SizeOf(NodePtrType));
Move(nextNode,pg[NEXTLOC],SizeOf(NodePtrType));
StorePage(iFName,fId,thisNode,pg);
if BTreeErrorOccurred then Exit;
if tempPrevNode <> NULL then
begin
FetchPage(iFName,fId,tempPrevNode,pg);
if BTreeErrorOccurred then Exit;
Move(thisNode,pg[NEXTLOC],SizeOf(NodePtrType));
StorePage(iFName,fId,tempPrevNode,pg);
if BTreeErrorOccurred then Exit;
end
else
begin (* new node is first node *)
if pg[NTYPELOC] = Byte(SEQUENCENODE) then
begin (* set first seq node pointer to this new node *)
pRec.fSNode := thisNode;
end;
end;
if tempNextNode <> NULL then
begin
FetchPage(iFName,fId,tempNextNode,pg);
if BTreeErrorOccurred then Exit;
Move(thisNode,pg[PREVLOC],SizeOf(NodePtrType));
StorePage(iFName,fId,tempNextNode,pg);
if BTreeErrorOccurred then Exit;
end
else
begin (* new node is last node *)
if pg[NTYPELOC] = Byte(SEQUENCENODE) then
begin (* set last seq node pointer to this new node *)
pRec.lSNode := thisNode;
end;
end;
end; (* end of InsertNodeInList routine *)
(*\*)
(* This routine will delete a node from a node list and set its neighbors prev
and next node pointers as appropriate. It will also delete the record from
the index file to allow it to be reused. *)
procedure DeleteNodeFromList(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
thisNode : NodePtrType;
var pRec : ParameterRecord);
var
pg : SinglePage;
prevNode,
nextNode : NodePtrType;
begin
FetchPage(iFName,fId,thisNode,pg);
if BTreeErrorOccurred then Exit;
Move(pg[PREVLOC],prevNode,SizeOf(NodePtrType)); (* get Prev node ptr *)
Move(pg[NEXTLOC],nextNode,SizeOf(NodePtrType)); (* get Next node ptr *)
if prevNode <> NULL then
begin
FetchPage(iFName,fId,prevNode,pg);
if BTreeErrorOccurred then Exit;
Move(nextNode,pg[NEXTLOC],RNSIZE);
StorePage(iFName,fId,prevNode,pg);
if BTreeErrorOccurred then Exit;
end
else
begin
if NodeType(pg[NTYPELOC]) = SEQUENCENODE then
begin
pRec.fSNode := nextNode;
end;
end;
if nextNode <> NULL then
begin
FetchPage(iFName,fId,nextNode,pg);
if BTreeErrorOccurred then Exit;
Move(prevNode,pg[PREVLOC],RNSIZE);
StorePage(iFName,fId,nextNode,pg);
if BTreeErrorOccurred then Exit;
end
else
begin
if NodeType(pg[NTYPELOC]) = SEQUENCENODE then
begin
pRec.lSNode := nextNode;
end;
end;
DeleteIndexRecord(iFName,fId,thisNode,pRec); (* get rid of phys rec *)
if BTreeErrorOccurred then Exit;
end; (* end of DeleteNodeFromList *)
(*\*)
(* This routine will create a new node and set the node type parameter
and will insert this node between the prev node and the next node
in the node linked list. Remember, this level linked list is required to
facilitate deletions. *)
function CreatedNode(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
var prevNode;
var nextNode;
nType : NodeType;
var pRec : ParameterRecord) : NodePtrType;
var
pg : SinglePage;
newNode : NodePtrType;
begin
newNode := FirstUnUsedIndexRecord(iFName,fId,pRec);
if BTreeErrorOccurred then Exit;
FillChar(pg,PAGESIZE,0);
pg[NTYPELOC] := Byte(nType); (* set the node type *)
StorePage(iFName,fId,newNode,pg); (* will create new node automatically *)
if BTreeErrorOccurred then Exit;
InsertNodeInList(iFName,fId,newNode,prevNode,nextNode,pRec);
if BTreeErrorOccurred then Exit;
CreatedNode := newNode; (* return the node ptr for this new node *)
end; (* end of CreatedNode routine *)
(* This routine will calculate and return the proper byte pointer position for
the given entry number. The byte pointer position will be equal to the
location of the node pointer, not the value. *)
function BytePointerPosition(cnt : Byte;
vSize : VSizeType) : PageRange;
begin
BytePointerPosition := ((cnt - 1) * (vSize + RNSIZE)) + 1;
end; (* end of BytePointerPosition *)
(*\*)
(* This routine will return the entry number for the first entry in the node
which has a value equal to paramValue. If no value matches paramValue, the
first entry which has a value greater than paramValue will be returned. If
paramValue is greater than the last value in the node, then the last entry
number + 1 will be returned. The routine will return 0 iff the particular
node contains no entries. *)
function BinarySearchEntry(var pg : SinglePage; (* var for speed only *)
var paramValue;
var pRec : ParameterRecord (* var for speed only *)
) : Byte;
var
startCnt,
midCnt,
maxCnt : Byte;
begin
maxCnt := pg[VCNTLOC];
if maxCnt = 0 then
begin
BinarySearchEntry := 0;
Exit;
end;
if CompareValues(pg[RNSIZE + 1],paramValue,pRec.vType) <> LESSTHAN then
begin
BinarySearchEntry := 1;
Exit;
end;
if CompareValues(pg[((maxCnt - 1) * (pRec.vSize + RNSIZE)) +
RNSIZE + 1],
paramValue,
pRec.vType) = LESSTHAN then
begin
BinarySearchEntry := maxCnt + 1;
Exit;
end;
startCnt := 1;
while startCnt < (maxCnt - 1) do
begin
midCnt := (maxCnt + startCnt) Div 2;
if CompareValues(pg[((midCnt - 1) * (pRec.vSize + RNSIZE)) +
RNSIZE + 1],
paramValue,
pRec.vType) = LESSTHAN then
begin
startCnt := midCnt;
end
else
begin
maxCnt := midCnt;
end;
end;
BinarySearchEntry := maxCnt;
end; (* end of BinarySearchEntry routine *)
(*\*)
(* This routine will search an index node and return the record number for the
next lower node corresponding to the paramValue. The returned node will
either be another index node or a sequence node.
Note : this assumes that there are lower nodes. Prior to calling this
routine check for an empty root *)
function FindNextLevelPtr(var pg : SinglePage; (* var for speed only *)
var paramValue;
var pRec : ParameterRecord (* var for speed only *)
) : NodePtrType;
var
cnt : Byte;
bytePtr : PageRange;
p : NodePtrType; (* temporarily holds pointer to return *)
begin
cnt := BinarySearchEntry(pg,paramValue,pRec);
if cnt = 0 then
begin
bytePtr := 1;
end
else
begin
bytePtr := BytePointerPosition(cnt,pRec.vSize);
end;
Move(pg[bytePtr],p,RNSIZE); (* ptr to be returned *)
FindNextLevelPtr := p;
end; (* end of FindNextLevelPtr routine *)
(* This recursive routine will start at the specified node (rNum) and work
down the tree until the correct sequence node is found. It will return
the record number of the sequence node.
This routine assumes that as long as an index node is not empty, there
should be one more pointer than there are values. In other words, there
is always a trailing valid pointer which takes care of the case of values
larger than the largest value in the tree.
This routine also assumes that some sequence node exists. This will not
work for an empty root. This must be checked by caller. *)
function FindSNode(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
rNum : NodePtrType;
var paramValue;
var pRec : ParameterRecord (* var for speed only *)
) : NodePtrType;
var
pg : SinglePage;
begin
FetchPage(iFName,fId,rNum,pg); (* get node *)
if BTreeErrorOccurred then Exit;
if NodeType(pg[NTYPELOC]) = INDEXNODE then
begin
FindSNode := FindSNode(iFName,
fId,
FindNextLevelPtr(pg,paramValue,pRec),
paramValue,pRec);
if BTreeErrorOccurred then Exit;
end
else
begin
FindSNode := rNum;
end;
end; (* end of FindSNode function *)
(*\*)
(* This routine inserts a new value into a node. It will locate the
proper place, move all values and pointers past the spot to allow room for
the new value and pointer, and insert the new value and pointer. If there
is a value equal to this new value, the new value will be inserted in
front of the old one. This routine will not work if there is not enough
room in the node. This must be checked prior to calling this routine.
This routine works with both sequence and index nodes. It assumes that the
proper page has been read in prior to this routine being called. This is
why a page is passed in as a parameter in lieu of physical record number.
This works for both index and sequence nodes *)
procedure InsertValueIntoNode(var pg : SinglePage;
var paramValue;
rNum : RecordNumber;
nextNode : NodePtrType; (* used for
INDEXNODEs only *)
var pRec : ParameterRecord);
var
cnt,
vCnt : Byte;
bytePtr : PageRange;
tempNode : NodePtrType;
begin
vCnt := pg[VCNTLOC]; (* get value count *)
cnt := BinarySearchEntry(pg,paramValue,pRec);
if cnt = 0 then
begin (* node is empty *)
bytePtr := 1;
end
else
begin
bytePtr := BytePointerPosition(cnt,pRec.vSize);
if NodeType(pg[NTYPELOC]) = INDEXNODE then
begin (* find correct place in index node *)
Move(pg[bytePtr],tempNode,RNSIZE);
while (tempNode <> nextNode) and (cnt <= vCnt) do
begin
bytePtr := bytePtr + pRec.vSize + RNSIZE;
Move(pg[bytePtr],tempNode,RNSIZE);
Inc(cnt);
end;
end;
end;
Move(pg[bytePtr], (* make room *)
pg[bytePtr + pRec.vSize + RNSIZE],
(((vCnt - cnt) + 1) * (pRec.vSize + RNSIZE)) + RNSIZE);
Move(rNum,pg[bytePtr],RNSIZE); (* insert pointer *)
Move(paramValue,pg[bytePtr + RNSIZE],pRec.vSize); (*insert value*)
pg[VCNTLOC] := vCnt + 1; (* new value count *)
if mustMoveCursor and (cnt <= pRec.cursor.entryNum) then
begin
Inc(pRec.cursor.entryNum);
end;
end; (* end of InsertValueIntoNode routine *)
(*\*)
(* This routine will calculate and return the maximum number of entries which
will fit in an index node. *)
function MaxEntries(vSize : VSizeType) : Byte;
begin
MaxEntries := (MAXUSABLE - RNSIZE) Div (vSize + RNSIZE);
end; (* end of MaxEntries routine *)
(* This routine will move n/2 (rounded down) values from the right node
(rtNode) to the empty left node (ltNode). *)
procedure MoveValues(var rtPage : SinglePage;
var ltPage : SinglePage;
ltNode : NodePtrType;
var pRec : ParameterRecord);
var
bytesToMove, (* total number of bytes to move *)
numToMove, (* number of values to move *)
vCnt : Byte; (* count of values in right node *)
begin
vCnt := rtPage[VCNTLOC]; (* get right node's count *)
numToMove := vCnt Div 2;
bytesToMove := (RNSIZE + pRec.vSize) * numToMove; (* calc # of bytes
to move *)
Move(rtPage[1],ltPage[1],bytesToMove);
Move(rtPage[bytesToMove + 1],rtPage[1],MAXUSABLE - bytesToMove);
Dec(vCnt,numToMove);
if NodeType(rtPage[NTYPELOC]) = INDEXNODE then
begin
FillChar(rtPage[(vCnt * (pRec.vSize + RNSIZE)) + 1 + RNSIZE],
numToMove * (pRec.vSize + RNSIZE),
0);
end
else
begin
FillChar(rtPage[(vCnt * (pRec.vSize + RNSIZE)) + 1],
numToMove * (pRec.vSize + RNSIZE),
0);
end;
rtPage[VCNTLOC] := vCnt;
ltPage[VCNTLOC] := numToMove;
if mustMoveCursor then
begin
if numToMove < pRec.cursor.entryNum then
begin
Dec(pRec.cursor.entryNum,numToMove);
end
else
begin
pRec.cursor.prNum := ltNode;
end;
end;
end; (* end of MoveValues routine *)
(*\*)
(* This recursive routine will start at a given node (usually the root) and
follow the tree down until the correct sequence node is found. The new
value and record number will be inserted into the correct sequence node.
In the event that the sequence node is full, the node will be split. The
value and record number will be put in the proper node if a split occurs.
The routine will return NULL if no split occurs. If a split occurs, the
record number (node pointer) of the newly created node will be returned.
This new node will be inserted in the parent index node. If it won't
fit the index node will be split and the new child record number will
be inserted in the proper index node. The value associated with the child
record number is the largest value in the newly created child node. This
process continues until we bubble back to the root in the node. Once at
the root the routine will return back to the original caller. If the root
was not split then NULL will be returned. If the root was split, then the
newly created child record number is returned. The caller will have to
create a new root node and insert the new value and the child record
number. Be sure that the caller also inserts the newly inserted child's
right sibling (record number only) since all indexes have one more pointer
than they do values.
This routine expects at least one pointer in the root. This needs to be
checked by the caller. *)
function InsertValue(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
rNum : RecordNumber; (* record number to be inserted *)
var paramValue; (* value to be inserted *)
thisNode : NodePtrType; (* node *)
var pRec : ParameterRecord) : NodePtrType;
var
newNode, (* newly created node if needed (node split) *)
lowerNode : NodePtrType;
thisPage,
newPage,
lowerPage : SinglePage;
lastValLoc : PageRange; (* used to hold buffer position *)
nextNode : NodePtrType;
comp : Comparison;
function NewPageContainsNodePtr : Boolean;
var
cnt,
bytePtr : PageRange;
tempNode : NodePtrType;
begin
bytePtr := 1;
for cnt := 1 to newPage[VCNTLOC] do
begin
Move(newPage[bytePtr],tempNode,RNSIZE);
if tempNode = nextNode then
begin
NewPageContainsNodePtr := TRUE;
Exit;
end;
bytePtr := bytePtr + pRec.vSize + RNSIZE;
end;
NewPageContainsNodePtr := FALSE;
end;
begin
FetchPage(iFName,fId,thisNode,thisPage);
if BTreeErrorOccurred then Exit;
case NodeType(thisPage[NTYPELOC]) of
INDEXNODE:
begin
lowerNode := InsertValue(iFName,fId,rNum,paramValue,
FindNextLevelPtr(thisPage,paramValue,
pRec),pRec);
if BTreeErrorOccurred then Exit;
if lowerNode <> NULL then
begin (* lower node must have been split *)
FetchPage(iFName,fId,lowerNode,lowerPage);
if BTreeErrorOccurred then Exit;
lastValLoc := (((lowerPage[VCNTLOC] - 1)
* ( pRec.vSize + RNSIZE)) + RNSIZE) + 1;
Move(lowerPage[NEXTLOC],nextNode,RNSIZE);
if MaxEntries(pRec.vSize) > thisPage[VCNTLOC] then
begin (* it fits *)
InsertValueIntoNode(thisPage,lowerPage[lastValLoc],
lowerNode,nextNode,pRec);
InsertValue := NULL; (* node not split .. return NULL *)
end
else
begin
newNode := CreatedNode(iFName,
fId,
thisPage[PREVLOC],
thisNode,
INDEXNODE,
pRec);
if BTreeErrorOccurred then Exit;
FetchPage(iFName,fId,thisNode,thisPage); (* required *)
if BTreeErrorOccurred then Exit;
FetchPage(iFName,fId,newNode,newPage);
if BTreeErrorOccurred then Exit;
MoveValues(thisPage,newPage,newNode,pRec);
comp := CompareValues(lowerPage[lastValLoc],
thisPage[RNSIZE + 1],
pRec.vType);(* which page is it in *)
if comp = EQUALTO then
begin
if NewPageContainsNodePtr then
begin
comp := LESSTHAN;
end
else
begin
comp := GREATERTHAN;
end;
end;
if comp = LESSTHAN then
begin
InsertValueIntoNode(newPage,
lowerPage[lastValLoc],
lowerNode,nextNode,pRec);
end
else
begin
InsertValueIntoNode(thisPage,
lowerPage[lastValLoc],
lowerNode,nextNode,pRec);
end;
StorePage(iFName,fId,newNode,newPage);
if BTreeErrorOccurred then Exit;
InsertValue := newNode; (* newly added node will be
returned *)
end;
StorePage(iFName,fId,thisNode,thisPage);
if BTreeErrorOccurred then Exit;
end
else
begin
InsertValue := NULL; (* it fit at lower level therefore
this level is fine .. return NULL *)
end;
end;
SEQUENCENODE :
begin
mustMoveCursor := pRec.cursor.valid and
(pRec.cursor.prNum = thisNode);
if MaxEntries(pRec.vSize) > thisPage[VCNTLOC] then
begin (* it fits *)
InsertValueIntoNode(thisPage,paramValue,rNum,NULL,pRec);
InsertValue := NULL; (* it fits .. no split .. return NULL *)
end
else
begin
newNode := CreatedNode(iFName,
fId,
thisPage[PREVLOC],
thisNode,
SEQUENCENODE,
pRec);
if BTreeErrorOccurred then Exit;
FetchPage(iFName,fId,thisNode,thisPage); (* required *)
if BTreeErrorOccurred then Exit;
FetchPage(iFName,fId,newNode,newPage);
if BTreeErrorOccurred then Exit;
MoveValues(thisPage,newPage,newNode,pRec);
if CompareValues(paramValue,thisPage[RNSIZE + 1],
pRec.vType) = GREATERTHAN then
begin
InsertValueIntoNode(thisPage,paramValue,rNum,NULL,pRec);
end
else
begin
InsertValueIntoNode(newPage,paramValue,rNum,NULL,pRec);
end;
StorePage(iFName,fId,newNode,newPage);
if BTreeErrorOccurred then Exit;
InsertValue := newNode;
end;
StorePage(iFName,fId,thisNode,thisPage);
mustMoveCursor := FALSE;
end;
end; (* end of case statement *)
end; (* end of InsertValue routine *)
(*\*)
(* This routine will locate and delete a value and its associated record
pointer from within a node/list of nodes. It will first locate the value.
The value will be found in this node or in succeeding nodes. The search will
continue until the value and the correct associated record number are found
or it is determined that it does not exist.If the correct value and record
number are not found then the routine will return FALSE indicating that no
value was deleted. If the correct value and record number are found they
will be deleted. In this case the node where the value was deleted from
will be returned. If the value deleted was the last in the node or the
only one in the node these facts will be returned. This is important
because the calling node may have to alter or delete values as a result.
note : if a node is the last node in a level node list the node will not
be deleted. In this case the value will be deleted but last will be set
to FALSE. This is because the parent needs to make no adjustment. *)
function DeleteValueFromNode(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
rNum : RecordNumber;
var paramValue;
var thisNode : NodePtrType;
var pRec : ParameterRecord;
var last : Boolean;
var nodeDeleted : Boolean) : Boolean;
var
done : Boolean;
cnt,
vCnt : Byte;
bytePtr : PageRange;
pg : SinglePage;
nextNode : NodePtrType;
recNum : RecordNumber;
begin
FetchPage(iFName,fId,thisNode,pg); (* fetch page for this node *)
if BTreeErrorOccurred then Exit;
vCnt := pg[VCNTLOC]; (* get value count *)
cnt := BinarySearchEntry(pg,paramValue,pRec);
if (cnt <> 0) and (cnt <= vCnt) then
begin
bytePtr := BytePointerPosition(cnt,pRec.vSize);
done := FALSE;
end
else
begin (* no such value in this node *)
DeleteValueFromNode := FALSE;
last := FALSE;
nodeDeleted := FALSE;
done := TRUE;
end;
while not done do
begin
if CompareValues(paramValue,
pg[bytePtr + RNSIZE],
pRec.vType) = LESSTHAN then
begin
done := TRUE;
DeleteValueFromNode := FALSE;
last := FALSE;
nodeDeleted := FALSE;
end
else
begin (* value found .. look for record number match *)
Move(pg[bytePtr],recNum,RNSIZE);
if rNum = recNum then
begin (* record number match found *)
done := TRUE;
DeleteValueFromNode := TRUE;
Move(pg[NEXTLOC],nextNode,RNSIZE);
if (vCnt = 1) and (nextNode <> NULL) then
begin (* only 1 entry in this node *)
last := TRUE;
nodeDeleted := TRUE;
end
else
begin
pg[VCNTLOC] := vCnt - 1;
Move(pg[bytePtr + RNSIZE + pRec.vSize],
pg[bytePtr],
(RNSIZE + pRec.vSize) * (vCnt - cnt) + RNSIZE);
FillChar(pg[(((vCnt - 1) * (pRec.vSize + RNSIZE)) + 1) +
RNSIZE],
pRec.vSize + RNSIZE,
0);
StorePage(iFName,fId,thisNode,pg); (* store the page *)
if BTreeErrorOccurred then Exit;
nodeDeleted := FALSE;
last := (cnt = vCnt) and (vCnt <> 1) and (nextNode <> NULL);
(* the nextNode check is used since the last node
at level only has a node pointer and not a
corresponding value at the next higher level.
Therefore, no value adjustment will be required *)
end;
if mustMoveCursor then
begin
if pRec.cursor.entryNum > cnt then
begin
Dec(pRec.cursor.entryNum);
end
else
begin
if pRec.cursor.entryNum = cnt then
begin
Dec(pRec.cursor.entryNum);
if pRec.cursor.entryNum = 0 then
begin
pREc.cursor.valid := FALSE;
end;
end;
end;
end;
end;
end;
if not done then
begin
if (cnt = vCnt) then
begin (* no more values .. get brother *)
Move(pg[NEXTLOC],thisNode,RNSIZE); (* get brother *)
if thisNode = NULL then
begin (* no brother .. quit *)
done := TRUE;
DeleteValueFromNode := FALSE;
last := FALSE;
nodeDeleted := FALSE;
end
else
begin (* brother found *)
FetchPage(iFName,fId,thisNode,pg); (* fetch brother *)
if BTreeErrorOccurred then Exit;
vCnt := pg[VCNTLOC];
if vCnt = 0 then (* check to see if node is empty *)
begin (* if so we are done *)
done := TRUE;
DeleteValueFromNode := FALSE;
last := FALSE;
nodeDeleted := FALSE;
end
else
begin
bytePtr := 1;
cnt := 1;
end;
end;
end
else
begin
Inc(cnt);
bytePtr := bytePtr + RNSIZE + pRec.vSize;
end;
end;
end;
end; (* end of DeleteValueFromNode routine *)
(*\*)
(* This recursive routine will start at a given node (initially the root) and
follow the tree down until the correct sequence node is found. Once it
is found DeleteValueFromNode is used to delete the value from the node if
it exists. The routine returns TRUE if the value (including the correct
physical record pointer) is found and deleted or if the last entry in the
node was changed to a new value because of a deletion in a lower node.
Otherwise, FALSE is returned. If this deletion causes an empty node,
DeleteValueFromNode will delete the node. This routine will take this into
account and delete the lowernode and lowernode node pointer. The variable
nodeDeleted will be set TRUE by DeleteValueFromNode to denote that the
lower node was deleted. If the lower node was not deleted but the value
deleted was the last value in the node (not the only value but the last)
and was not the last node of a given level then this routine will change
the value pointing to the lower node to take this into account. This will
be noted by last being set to TRUE by DeleteValueFromNode. *)
function DeleteValue(var iFName : FnString; (* var for speed only *)
var fId : File; (* var for speed only *)
rNum : RecordNumber;
var paramValue;
var thisNode : NodePtrType;
var pRec : ParameterRecord;
var last : Boolean;
var nodeDeleted : Boolean) : Boolean;
var
lowerPage,
thisPage : SinglePage;
lastValLoc,
bytePtr : PageRange;
lowerNode : NodePtrType;
cnt : Byte;
begin
FetchPage(iFName,fId,thisNode,thisPage);
if BTreeErrorOccurred then Exit;
case NodeType(thisPage[NTYPELOC]) of
INDEXNODE :
begin
lowerNode := FindNextLevelPtr(thisPage,paramValue,pRec);
if DeleteValue(iFName,
fId,
rNum,
paramValue,
lowerNode, (* will become the lower node where
the value was deleted from *)
pRec,
last,
nodeDeleted) then
begin (* value was successfully deleted from node below *)
if BTreeErrorOccurred then Exit;
if nodeDeleted then (* check to see if lower node deleted *)
begin (* it was - delete corresponding node
pointer from this node *)
DeleteValue := DeleteValueFromNode(iFName,
fId,
lowerNode, (*lower node
pointer *)
paramValue,
thisNode, (* node to
delete
from *)
pRec,
last,
nodeDeleted);
if BTreeErrorOccurred then Exit;
DeleteNodeFromList(iFName,fId,lowerNode,pRec);
(* delete lower node from list *)
if BTreeErrorOccurred then Exit;
end
else
begin (* node not deleted *)
if last then (* value deleted was last entry in
lower node and lower node was not
last at that level .. therefore we
need to change the value
corresponding to the new last
value in the lower node *)
begin
bytePtr := 1;
cnt := BinarySearchEntry(thisPage,paramValue,pRec);
bytePtr := BytePointerPosition(cnt,pRec.vSize);
(* now find record number match *)
if CompareValues(lowerNode,
thisPage[bytePtr],
LONGINTVALUE) = EQUALTO then
(* It is not obvious, but if the first entry
for paramValue is not the one that matches
the lower node, no adjustment will be
required. This is because, if the lower node
is not the first node with this value, the
new last value for the lower node will be
paramValue *)
begin
FetchPage(iFName,fId,lowerNode,lowerPage);
if BTreeErrorOccurred then Exit;
lastValLoc := ((lowerPage[VCNTLOC] - 1)
* (pRec.vSize + RNSIZE)) + 1;
Move(lowerPage[lastValLoc + RNSIZE],
thisPage[bytePtr + RNSIZE],
pRec.vSize);
StorePage(iFName,fId,thisNode,thisPage);
if BTreeErrorOccurred then Exit;
last := (cnt = thisPage[VCNTLOC]);
end
else
begin
last := FALSE;
end;
end;
DeleteValue := last;
end;
end
else
begin (* no deletion/adjustment performed at lower level *)
if BTreeErrorOccurred then Exit;
DeleteValue := FALSE;
end;
end;
SEQUENCENODE :
begin
mustMoveCursor := pRec.cursor.valid and
(pRec.cursor.prNum = thisNode);
DeleteValue := DeleteValueFromNode(iFName,fId,rNum,paramValue,
thisNode,pRec,last,nodeDeleted);
mustMoveCursor := FALSE;
end;
end; (* end of case statement *)
end; (* end of DeleteValue routine *)
{$I dfbtree.inc} (* The rest of the btree routines *)
begin
mustMoveCursor := FALSE;
end. (* end of BTree unit *)